#####################################################################
# Product:      KnowledgeEngine (Plone Edition)
#
# File:         Graphable.py
#
# Copyright:    Copyright (c) 2005, Faulkner Technologies
#
# Author:       Brian Oliver
#
# Description:  This file defines a simple data structure for
#               representing graphable information.
#
#               NOTE: This class is not persistent and does not
#               actually render graphs
#
# See Also:     Datum
#####################################################################

#####################################################################
## Knowledge Engine Library Imports
from Products.KnowledgeEngine.Common import *
from Products.KnowledgeEngine.Core import *

#####################################################################
## Zope Library Imports
from Globals import InitializeClass
from AccessControl import ClassSecurityInfo
from AccessControl.Role import RoleManager
from DateTime import DateTime
 
#####################################################################
## Python Library Imports
import os

#####################################################################
## Graphable Class Definition
class Graphable(RoleManager):
    """
        A lazily in-memory storage-agnostic representation of 
        Graphable information.
    """
        
    #---------------------------------------------------------------#

    #declare security so ClassInfo may be accessed in DTML/ZPT etc
    security = ClassSecurityInfo()
    security.declareObjectPublic()
    security.setDefaultAccess("allow")

    #---------------------------------------------------------------#

    def __init__(self, title = "", description = "", minimumValue = None, maximumValue = None):
        """
            Default constructor for a Graphable
        """
        
        self.title = title
        self.description = description
        self.minimumValue = minimumValue
        self.maximumValue = maximumValue
        
        #a list to contain the order in which datums have been added
        #to the graphable
        self.datumIdentityList = []      
        
        #a {datum.getIdentity() -> Datum} map to hold the actual
        #datums that have been added to the datum
        self.datumMap = {}
        
    #---------------------------------------------------------------#

    def setTitle(self, title):
        """
            Set the title for the Graphable
        """
        
        self.title = title
        
    #---------------------------------------------------------------#

    def getTitle(self):
        """
            Return the title for the Graphable
        """
        
        return self.title
        
    #---------------------------------------------------------------#

    def getDescription(self):
        """
            Return the description for the Graphable
        """
        
        return self.description
        
    #---------------------------------------------------------------#

    def getMinimumValue(self):
        """
            Return the minimum value for the Graphable.
            
            If a minimum was specified when the Graphable was created,
            that is used.  Otherwise it is calculated from
            the Datums.
        """
        
        if self.minimumValue is not None:
            return float(self.minimumValue)
            
        return float(min([datum.getValue() for datum in datumMap.values()]))            
        
    #---------------------------------------------------------------#

    def getMaximumValue(self):
        """
            Return the maximum value for the Graphable
            
            If a maximum was specified when the Graphable was created,
            that is used.  Otherwise it is calculated from
            the Datums.
        """
        
        if self.maximumValue is not None:
            return float(self.maximumValue)
            
        return float(max([datum.getValue() for datum in datumMap.values()]))            
        
    #---------------------------------------------------------------#

    def addDatum(self, datum):
        """
            Add the specified datum to the graphables datum.
            
            If a datum with the same datum identity already exists 
            in the current collection, the existing datum is over 
            written
        """

        #ensure the datum has an identity
        if datum.getIdentity() is None:
            datum.setIdentity(generateIdentity("datum"))
            
        identity = datum.getIdentity()
        
        if not self.datumMap.has_key(identity):
            self.datumIdentityList.append(identity)
            
        self.datumMap[identity] = datum
        
    #---------------------------------------------------------------#

    def getDatums(self):
        """
            Return a collection of the currently added datums
            in the Graphable, in order that they where added
            
            RETURN TYPE: [Datum]
        """
        
        return [self.datumMap[identity] for identity in self.datumIdentityList]
        
    #---------------------------------------------------------------#

    def getFootnoteNumber(self, datum):
        """
            Return a unique footnote number for the specified datum.
            
            Returns 0 if the datum does not require a footnote
        """            

        count = 0
        for datumIdentity in self.datumIdentityList:
            if self.datumMap[datumIdentity].hasFootnote():
                count = count + 1
            if datumIdentity == datum.getIdentity():
                return count

        return 0
        
    #---------------------------------------------------------------#
        
    def addGraphable(self, otherGraphable):
        """
            Adds the specified graphable datums to this graphable
        """            

        #get the datums from the other graphable.. we will merge these suckers
        otherDatums = otherGraphable.getDatums()
        
        for otherDatum in otherDatums:
            #add a clone of the other datum (to ensure if we change it we don't cause side-effects)
            self.addDatum(otherDatum.clone())
        
    #---------------------------------------------------------------#
        
#####################################################################
## Class Initialisation
InitializeClass(Graphable)
